#include "skybox.h"
#include "helper.h"

Skybox::Skybox() :
	pVertexBuffer(NULL), vertexLayout(NULL), effect(NULL), WorldVariable(NULL),
			ViewVariable(NULL), ProjectionVariable(NULL),
			pTextureVariable(NULL) {
}

Skybox::~Skybox() {
}

bool Skybox::Initialise(ID3D10Device *device) {
	if (!InitialiseShader(device))
		return false;

	// create scene objects
	if (!InitialiseGeometry(device))
		return false;

	if (!LoadTextures(device))
		return false;

	return true;
}

void Skybox::Cleanup() {
	Release(pVertexBuffer);

	ID3D10ShaderResourceView* tex = 0;
	for (UINT i = 0; i < textures.size(); i++) {
		tex = textures[i];
		ID3D10Resource *pRes;
		tex->GetResource(&pRes);
		Release(pRes);
		Release(tex);
	}

	Release(effect);
	effectTechnique = NULL;
	WorldVariable = NULL;
	ViewVariable = NULL;
	ProjectionVariable = NULL;
}

void Skybox::Render(ID3D10Device *device) {
	using namespace pt;
	// Set Vertex Buffer
	UINT stride = sizeof(pt::Vertex);
	UINT offset = 0u;
	device->IASetVertexBuffers(0u, 1u, &pVertexBuffer, &stride, &offset);
	// Set the input layout
	device->IASetInputLayout(vertexLayout);
	device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);

	D3D10_TECHNIQUE_DESC techDesc;
	effectTechnique->GetDesc(&techDesc);

	Vertex* v = NULL;
	pVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**) &v);
	// front
	v[0] = CreateVertex(D3DXVECTOR3(-100.0f, -100.0f, 100.0f), D3DXVECTOR2(
			0.0f, 1.0f));
	v[1] = CreateVertex(D3DXVECTOR3(-100.0f, 100.0f, 100.0f), D3DXVECTOR2(0.0f,
			0.0f));
	v[2] = CreateVertex(D3DXVECTOR3(100.0f, -100.0f, 100.0f), D3DXVECTOR2(1.0f,
			1.0f));
	v[3] = CreateVertex(D3DXVECTOR3(100.0f, 100.0f, 100.0f), D3DXVECTOR2(1.0f,
			0.0f));

	// Back
	v[4] = CreateVertex(D3DXVECTOR3(100.0f, -100.0f, -100.0f), D3DXVECTOR2(
			0.0f, 1.0f));
	v[5] = CreateVertex(D3DXVECTOR3(100.0f, 100.0f, -100.0f), D3DXVECTOR2(0.0f,
			0.0f));
	v[6] = CreateVertex(D3DXVECTOR3(-100.0f, -100.0f, -100.0f), D3DXVECTOR2(
			1.0f, 1.0f));
	v[7] = CreateVertex(D3DXVECTOR3(-100.0f, 100.0f, -100.0f), D3DXVECTOR2(
			1.0f, 0.0f));

	// Left
	v[8] = CreateVertex(D3DXVECTOR3(-100.0f, -100.0f, -100.0f), D3DXVECTOR2(
			0.0f, 1.0f));
	v[9] = CreateVertex(D3DXVECTOR3(-100.0f, 100.0f, -100.0f), D3DXVECTOR2(
			0.0f, 0.0f));
	v[10] = CreateVertex(D3DXVECTOR3(-100.0f, -100.0f, 100.0f), D3DXVECTOR2(
			1.0f, 1.0f));
	v[11] = CreateVertex(D3DXVECTOR3(-100.0f, 100.0f, 100.0f), D3DXVECTOR2(
			1.0f, 0.0f));

	// Right
	v[12] = CreateVertex(D3DXVECTOR3(100.0f, -100.0f, 100.0f), D3DXVECTOR2(
			0.0f, 1.0f));
	v[13] = CreateVertex(D3DXVECTOR3(100.0f, 100.0f, 100.0f), D3DXVECTOR2(0.0f,
			0.0f));
	v[14] = CreateVertex(D3DXVECTOR3(100.0f, -100.0f, -100.0f), D3DXVECTOR2(
			1.0f, 1.0f));
	v[15] = CreateVertex(D3DXVECTOR3(100.0f, 100.0f, -100.0f), D3DXVECTOR2(
			1.0f, 0.0f));

	// Top
	v[16] = CreateVertex(D3DXVECTOR3(-100.0f, 100.0f, 100.0f), D3DXVECTOR2(
			0.0f, 1.0f));
	v[17] = CreateVertex(D3DXVECTOR3(-100.0f, 100.0f, -100.0f), D3DXVECTOR2(
			0.0f, 0.0f));
	v[18] = CreateVertex(D3DXVECTOR3(100.0f, 100.0f, 100.0f), D3DXVECTOR2(1.0f,
			1.0f));
	v[19] = CreateVertex(D3DXVECTOR3(100.0f, 100.0f, -100.0f), D3DXVECTOR2(
			1.0f, 0.0f));

	// Bottom
	v[20] = CreateVertex(D3DXVECTOR3(-100.0f, -100.0f, -100.0f), D3DXVECTOR2(
			0.0f, 1.0f));
	v[21] = CreateVertex(D3DXVECTOR3(-100.0f, -100.0f, 100.0f), D3DXVECTOR2(
			0.0f, 0.0f));
	v[22] = CreateVertex(D3DXVECTOR3(100.0f, -100.0f, -100.0f), D3DXVECTOR2(
			1.0f, 1.0f));
	v[23] = CreateVertex(D3DXVECTOR3(100.0f, -100.0f, 100.0f), D3DXVECTOR2(
			1.0f, 0.0f));
	pVertexBuffer->Unmap();

	for (UINT i = 0u; i < 6u; ++i) {
		pTextureVariable->SetResource(textures[i]);
		for (UINT p = 0; p < techDesc.Passes; ++p) {
			effectTechnique->GetPassByIndex(p)->Apply(0u);
			device->Draw(4u, i * 4u);
		}
	}
}

void Skybox::SetShaderVars(D3DXMATRIX World, D3DXMATRIX View,
		D3DXMATRIX Projection) {
	WorldVariable->SetMatrix(World);
	ViewVariable->SetMatrix(View);
	ProjectionVariable->SetMatrix(Projection);
}

bool Skybox::InitialiseGeometry(ID3D10Device *device) {
	UINT nVertexCount = 25u;

	D3D10_BUFFER_DESC bufferDesc;
	bufferDesc.Usage = D3D10_USAGE_DYNAMIC;
	bufferDesc.ByteWidth = sizeof(pt::Vertex) * nVertexCount;
	bufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
	bufferDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
	bufferDesc.MiscFlags = 0u;

	HRESULT hr = device->CreateBuffer(&bufferDesc, NULL, &pVertexBuffer);
	if (FAILED(hr)) {
		return Error(L"Could not create Vertex Buffer");
	}

	return true;
}

bool Skybox::InitialiseShader(ID3D10Device *device) {
	HRESULT hr = S_OK;
	ID3D10Blob *pErrors = NULL;

	hr = D3DX10CreateEffectFromFile(L"shaders\\skybox.fx", NULL, NULL,
			"fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, device, 0, 0, &effect,
			&pErrors, &hr);

	if (FAILED(hr)) {
		if (pErrors) {
			LPVOID compileErrors = pErrors->GetBufferPointer();
			using namespace std;
			string strErr = (const char*) compileErrors;
			wstring wcstrErr(strErr.begin(), strErr.end());
			return Error((WCHAR*) wcstrErr.c_str(), L"Shader Compile Errors.");
		} else {
			return Error(L"Shader file missing!");
		}
	}

	effectTechnique = effect->GetTechniqueByName("Render");
	if (effectTechnique == NULL)
		return Error(L"Could not acquire the technique", L"Shader Error!");

	// obtain shader variables
	ObtainShaderVars();

	// create input layout
	if (!CreateInputLayout(device))
		return false;

	return true;
}

bool Skybox::CreateInputLayout(ID3D10Device *device) {
	using namespace pt;
	D3D10_PASS_DESC pDesc;
	effectTechnique->GetPassByIndex(0)->GetDesc(&pDesc);

	HRESULT hr = device->CreateInputLayout(layout, numElements,
			pDesc.pIAInputSignature, pDesc.IAInputSignatureSize, &vertexLayout);

	if (FAILED(hr))
		return Error(L"Could not create input layout");

	return true;
}

void Skybox::ObtainShaderVars() {
	WorldVariable = effect->GetVariableByName("World")->AsMatrix();
	ViewVariable = effect->GetVariableByName("View")->AsMatrix();
	ProjectionVariable = effect->GetVariableByName("Projection")->AsMatrix();
	pTextureVariable
			= effect->GetVariableByName("txDiffuse")->AsShaderResource();
}

bool Skybox::LoadTextures(ID3D10Device *device) {
	HRESULT hr = S_OK;
	LP names[] = {
		L"media\\textures\\skybox\\front.jpg",
		L"media\\textures\\skybox\\back.jpg",
		L"media\\textures\\skybox\\left.jpg",
		L"media\\textures\\skybox\\right.jpg",
		L"media\\textures\\skybox\\top.jpg",
		L"media\\textures\\skybox\\bottom.jpg",
	};

	for (UINT i = 0u; i < ARRAYSIZE(names); ++i) {
		ID3D10ShaderResourceView* tex = 0;
		hr |= D3DX10CreateShaderResourceViewFromFile(device, names[i], NULL,
				NULL, &tex, NULL);
		if (FAILED(hr)) {
			return Error(L"Some of the mesh textures could not be loaded",
					names[i]);
		}
		textures.push_back(tex);
	}

	return true;
}
